/****************************************************************************
Copyright (c) 2023 - 2024 Qualcomm Technologies International, Ltd.


FILE NAME
    upgrade_fw_if_config.c

DESCRIPTION
    Configuration dependent functions which (largely) interact with the firmware.

NOTES

*/

#define DEBUG_LOG_MODULE_NAME upgrade
#include <logging.h>
#include <stdlib.h>
#include <string.h>
#include <logging.h>
#include <sink.h>
#include <stream.h>
#include <panic.h>
#include <boot.h>
#include <byte_utils.h>
#include <hydra_macros.h>
#include <imageupgrade.h>
#include "upgrade_fw_if.h"
#include "ecdsa_key_constants.h"
#include "upgrade_fw_if_priv.h"
#include "ra_partition_api.h"
#include "onchip_coredump.h"

uint8 UpgradePartitionDataGetSigningMode(void);

static hash_algo_t upgradeFWIFMapHashAlgoType(upgrade_fw_if_hash_algo_t algo)
{
    return (algo == UPGRADE_FW_IF_SHA256? SHA256_ALGORITHM : SHA384_ALGORITHM);
}

static sig_verify_algo_t upgradeFWIFMapSigAlgoType(upgrade_fw_if_signature_algo_t algo)
{
    /* only one algo supported as of now. */
    return ECC384_ALGORITHM;
}

/******************************************************************************
NAME
    UpgradeFWIFGetPhysPartitionSize

DESCRIPTION
    Find out the size of a specified partition in bytes.
    
PARAMS
    physPartition Physical partition number in external flash.
    
RETURNS
    uint32 Size of physPartition in bytes.
*/
uint32 UpgradeFWIFGetPhysPartitionSize(UpgradePartition physPartition)
{
    uint32 size;
    DEBUG_LOG_INFO("UpgradeFWIFGetPhysPartitionSize partition ID %d, section ID %d", physPartition.partitionInstance.partitionID, physPartition.partitionInstance.instanceID);
    if(ImageUpgradeInstanceGetInfo(physPartition.partitionInstance.partitionID, physPartition.partitionInstance.instanceID, IMAGE_SIZE, &size))
    {
        return (size);
    }
    return 0;
}

/***************************************************************************
NAME
    UpgradeFWIFPartitionOpen

DESCRIPTION
    Open a write only handle to a physical partition on the external flash.

PARAMS
    physPartition Physical partition number in external flash.
    firstWord First word of partition data.

RETURNS
    UpgradeFWIFPartitionHdl A valid handle or NULL if the open failed.
*/
UpgradeFWIFPartitionHdl UpgradeFWIFPartitionOpen(UpgradePartition physPartition, uint32 firstWord)
{
    Sink sink;

    DEBUG_LOG_DEBUG("UpgradeFWIFPartitionOpen: opening partition %u for resume", UpgradeFWIFSerializePartitionID(physPartition));
    if(UpgradeFWIIFIsUpgradableRAPartition(physPartition))
    {
        sink = StreamImageRaPartitionStreamGetSink(physPartition.partitionInstance.instanceID, firstWord);
    }
    else
    {
        sink = ImageUpgradeInstanceStreamGetSink(physPartition.partitionInstance.partitionID, physPartition.partitionInstance.instanceID, firstWord);
    }
    DEBUG_LOG_DEBUG("UpgradeFWIFPartitionOpen: ImageUpgradeStreamGetSink(%d, %d, 0x%08lx) returns %p\n", physPartition.partitionInstance.partitionID, physPartition.partitionInstance.instanceID, firstWord, sink);
    if (!sink)
    {
        DEBUG_LOG_ERROR("UpgradeFWIFPartitionOpen: failed to open raw partition %u for resume", UpgradeFWIFSerializePartitionID(physPartition));
        return (UpgradeFWIFPartitionHdl)NULL;
    }
    SinkConfigure(sink, VM_SINK_MESSAGES, VM_MESSAGES_NONE);

    UpgradeCtxGetFW()->partitionNum = physPartition;

    return (UpgradeFWIFPartitionHdl)sink;
}

/****************************************************************************
NAME
    UpgradeFWIFCommitUpgrades

DESCRIPTION
    Commits the new image as the boot image.

RETURNS
    n/a
*/
void UpgradeFWIFCommitUpgrades(void)
{
    image_upgrade_commit_status result = ImageUpgradeInstanceSwapCommit();

    UpgradeCtxGetFW()->commit_status = ((result == IMAGE_UPGRADE_COMMIT_FAILED)? UPGRADE_FW_IF_COMMIT_FAILED:
                                        (result == IMAGE_UPGRADE_COMMIT_SUCCESS_SECURITY_UPDATE_FAILED)? UPGRADE_FW_IF_COMMIT_SUCCESS_SECURITY_UPDATE_FAILED:
                                         UPGRADE_FW_IF_COMMIT_AND_SECURITY_UPDATE_SUCCESS);
    DEBUG_LOG_DEBUG("UpgradeFWIFCommitUpgrades: ImageUpgradeSwapCommit() = %u", result);
}

bool UpgradeFWIFHashSection(upgrade_fw_if_hash_algo_t algo,
                            UpgradePartition section,
                            uint32 section_length,
                            uint8 *hash,
                            uint16 hash_len_bytes,
                            bool *is_result_ready)
{
    UpgradeFWIFCtx *fwctx = UpgradeCtxGetFW();
    
    DEBUG_LOG_INFO("UpgradeFWIFHashSection Algo:%u, PartNum %u PartInstance %u, Section Length %d Hash Length %d",
        algo, section.partitionInstance.partitionID, section.partitionInstance.instanceID, section_length, hash_len_bytes);

    if(fwctx->hctx != NULL)
    {
        DEBUG_LOG_ERROR("UpgradeFWIFHashSection hash context not NULL %u", fwctx->hctx);
        return FALSE;
    }

    *is_result_ready = FALSE;
    fwctx->hctx = ImageUpgradeInstanceHashInitialise(upgradeFWIFMapHashAlgoType(algo));

    if (fwctx->hctx == NULL)
    {
        DEBUG_LOG_ERROR("UpgradeFWIFHashSection ImageUpgradeInstanceHashInitialise returned NULL");
        return FALSE;
    }
    ImageUpgradeInstanceHashSectionUpdate(fwctx->hctx, 
                                                 section.partitionInstance.partitionID, 
                                                 section.partitionInstance.instanceID,
                                                 section_length);
    return TRUE;
}

bool UpgradeFWIFHashData(upgrade_fw_if_hash_algo_t algo,
                        const uint8 *msg,
                        uint16 msg_len_bytes,
                        uint8 *hash,
                        uint16 hash_len_bytes,
                        bool *is_result_ready)
{
    hash_context_t hctx = ImageUpgradeInstanceHashInitialise(upgradeFWIFMapHashAlgoType(algo));

    if (hctx == NULL)
    {
        return FALSE;
    }

    if(!ImageUpgradeInstanceHashMsgUpdate(hctx, msg, msg_len_bytes))
    {
        return FALSE;
    }

    *is_result_ready = TRUE;

    return ImageUpgradeInstanceHashFinalise(hctx, hash, hash_len_bytes);
}

/*!
    @brief Calculate hash of all available sections.

    @note This API is not planned to be used during DFU but it will be provided for future use
          as the underlying trap exists.
*/
bool UpgradeFWIFHashAllSections(upgrade_fw_if_hash_algo_t algo,
                                uint8 *hash,
                                uint16 hash_len_bytes,
                                bool *is_result_ready)
{
    UNUSED(algo);
    UNUSED(hash);
    UNUSED(hash_len_bytes);
    UNUSED(is_result_ready);
    return FALSE;
}

bool UpgradeFWIFHashGetPendingResult(uint8 *hash,
                                     uint16 hash_len_bytes)
{
    UpgradeFWIFCtx *fwctx = UpgradeCtxGetFW();
    bool result;

    if(fwctx->hctx == NULL)
    {
        return FALSE;
    }

    result = ImageUpgradeInstanceHashFinalise(fwctx->hctx, hash, hash_len_bytes);
    fwctx->hctx = NULL;
    return result;
}

bool UpgradeFWIFSignatureVerify(upgrade_fw_if_signature_algo_t algo,
                                const uint8 *hash, uint32 hash_len, 
                                const uint8 *signature, uint32 signature_len)
{

    return ImageUpgradeInstanceVerifySignature(upgradeFWIFMapSigAlgoType(algo), 
                                       ecdsa_public_key_buf,
                                       ECDSA_FILE_SIGNATURE_SIZE,
                                       hash,
                                       hash_len,
                                       signature,
                                       signature_len);
}

UpgradePartitionsState UpgradeFWIFEraseSection(upgrade_fw_if_section_filter_t filter)
{
    if (filter == UPGRADE_FW_IF_FILTER_IMAGE_HEADER)
    {
        ImageUpgradeInstanceEraseSection(IMAGE_SECTION_IMAGE_HEADER, 0);
        return UPGRADE_PARTITIONS_ERASING_HEADER;
    }
    else if (filter ==  UPGRADE_FW_IF_FILTER_BANK_SECTIONS)
    {
        ImageUpgradeInstanceEraseSection(IMAGE_SECTION_ALL_EXCLUDE_HEADER, 0);
        return UPGRADE_PARTITIONS_ERASING;
    }
    else if (filter == UPGRADE_FW_IF_FILTER_UPGRADABLE_RA_PARTITION)
    {
        ImageUpgradeInstanceEraseSection(IMAGE_SECTION_RA_PARTITION, upgradeFWIFGetConfig()->upgradable_ra_partition_instance);
        return UPGRADE_PARTITIONS_ERASING;
    }
    else if (filter == UPGRADE_FW_IF_FILTER_UPGRADABLE_RA_PARTITION_IMAGE_HEADER)
    {
        UpgradeFWIFClearRAPartitionImageHeader();
        return UPGRADE_PARTITIONS_ERASED;
    }
    return UPGRADE_PARTITIONS_DIRTY;
}

void UpgradeFWIFClearRAPartitionImageHeader(void)
{
    raPartition_handle handle;
    raPartition_info info;
    if(RaPartitionInstanceOpen(upgradeFWIFGetConfig()->upgradable_ra_partition_instance, &handle, &info) != RA_PARTITION_RESULT_SUCCESS)
    {
        DEBUG_LOG("UpgradeFWIFClearRAPartitionImageHeader: RA partition open failed");
        return;
    }
    
    RaPartitionErase(&handle, 0);
    RaPartitionClose(&handle);
}

bool UpgradeFWIFIsImageHeader(UpgradePartition section)
{
    if (section.partitionInstance.partitionID == IMAGE_SECTION_IMAGE_HEADER && section.partitionInstance.instanceID == 1)
        return TRUE;
    return FALSE;
}

bool UpgradeFWIFSwapTryStatus(void)
{
    return ImageUpgradeInstanceSwapTryStatus();
}

/****************************************************************************
NAME
    UpgradeFWIFUpdateARBBits

DESCRIPTION
    Update ARB bits to ensure versions cannot be downgraded after a security upgrade

RETURN
    None
*/
void UpgradeFWIFUpdateARBBits(void)
{
    bool result = ImageUpgradeUpdateARBBits();
    UpgradeCtxGetFW()->commit_status = (result) ? UPGRADE_FW_IF_COMMIT_AND_SECURITY_UPDATE_SUCCESS : UPGRADE_FW_IF_COMMIT_SUCCESS_SECURITY_UPDATE_FAILED;

    DEBUG_LOG_DEBUG("UpgradeFWIFUpdateARBBits: ImageUpgradeUpdateARBBits() = %u", result);
}


/****************************************************************************
NAME
    UpgradeFWIFDFUReboot

DESCRIPTION
    Reboots device using ImageUpgradeInstanceSwapTry if DFU is for Native device, so the device boots 
    from the image bank, or BootSetMode if DFU is for dependent device so device reboots from the boot 
    bank itself.

RETURNS
    n/a
*/
void UpgradeFWIFDFUReboot(void)
{
    dfu_type_t dfu_type = UpgradeFWIFGetDFUType();
    /* If DFU in progress is for WifiSS then reboot device from boot bank */
    if(dfu_type == DFU_TYPE_WIFISS)
    {
        BootSetMode(BootGetMode());
    }
    else if(dfu_type == DFU_TYPE_NATIVE || dfu_type == DFU_TYPE_COMBO_USING_RA)
    {
        /* Reset the chip, load and run using the images from the other image bank. */
        ImageUpgradeInstanceSwapTry();
    }
    else
    {
        DEBUG_LOG_ERROR("UpgradeFWIFDFUReboot : UpgradeFWIFGetDFUType failed as partitions not written to alternate bank, aborting DFU");
        UpgradeFatalErrorInternalError1();
    }
}

bool UpgradeFWIFAbortCommand(void)
{
    return ImageUpgradeInstanceAbortCommand();
}

/***************************************************************************
NAME
    UpgradeFWIFGetDFUType

DESCRIPTION
    Get DFU type based on the partition header.

RETURNS
    Type of ongoing DFU
*/
dfu_type_t UpgradeFWIFGetDFUType(void)
{
    image_upgrade_dfu_type_t dfu_bank_image_type = ImageUpgradeInstanceGetDFUType();
    if(dfu_bank_image_type == IMAGE_UPGRADE_DFU_TYPE_NATIVE)
    {
        if(upgradeFWIFGetConfig() != NULL && ImageUpgradeInstanceGetRaPartitionDFUType(upgradeFWIFGetConfig()->upgradable_ra_partition_instance) == IMAGE_UPGRADE_DFU_TYPE_WIFISS)
        {
            return DFU_TYPE_COMBO_USING_RA;
        }
        else
        {
            return DFU_TYPE_NATIVE;
        }
    }
    else if(dfu_bank_image_type == IMAGE_UPGRADE_DFU_TYPE_WIFISS)
    {
        return DFU_TYPE_WIFISS;
    }
    return DFU_TYPE_NONE;
}

bool UpgradeFWIFGetSinkPosition(UpgradeFWIFPartitionHdl sink, uint32* offset)
{
    DEBUG_LOG_INFO("UpgradeFWIFGetSinkPosition: sink %x offset %x", sink, offset);
    if(UpgradeFWIIFIsUpgradableRAPartition(UpgradeCtxGetFW()->partitionNum))
    {
        return StreamImageRaPartitionSinkGetPosition((Sink) sink, offset);
    }
    else
    {
        return ImageUpgradeInstanceSinkGetPosition((Sink) sink, offset);
    }
}

UpgradePartition UpgradeFWIFIncreaseUpgradePartition(UpgradePartition partition)
{
    partition.partitionInstance.instanceID += 1;
    partition.partitionInstance.partitionID += 1;
    return partition;
}

UpgradePartition UpgradeFWIFBytesToUpgradePartition(const uint8* data)
{
    UpgradePartition partition;
    partition.partitionInstance.partitionID = data[0];
    partition.partitionInstance.instanceID = data[1];

    return partition;
}

short UpgradeFWIFCmpWithLastClosedUpgradePartition(UpgradePartition partition, UpgradePartition last_closed_partition)
{
    DEBUG_LOG("UpgradeFWIFCmpUpgradePartition: partition1 %d inst1 %d partition2 %d inst2 %d", 
    partition.partitionInstance.partitionID, partition.partitionInstance.instanceID, 
    last_closed_partition.partitionInstance.partitionID, last_closed_partition.partitionInstance.instanceID);
    UpgradePartition last_closed = {.partitionInstance.partitionID = last_closed_partition.partitionInstance.partitionID - 1, 
                                    .partitionInstance.instanceID = last_closed_partition.partitionInstance.instanceID - 1};

    if(last_closed_partition.partitionInstance.partitionID == 0 && last_closed_partition.partitionInstance.instanceID == 0)
    {
        /* we haven't closed any partition yet so, partition1 will be either equal or higher. */
        return 1;
    }

    if((partition.partitionInstance.partitionID < last_closed.partitionInstance.partitionID) ||
       (partition.partitionInstance.partitionID == last_closed.partitionInstance.partitionID && 
        partition.partitionInstance.instanceID <= last_closed.partitionInstance.instanceID))
    {
        /* partition1 is lesser partition than partition2 */
        return -1;
    }
    else
    {
        return 1;
    }
}

uint16 UpgradeFWIFSerializePartitionID(UpgradePartition partition)
{
    return (partition.partitionInstance.partitionID * 100 + partition.partitionInstance.instanceID);
}

/*!
    @brief UpgradePartitionsEraseAllManaged

    Erases partitions managed by the upgrade manager in preparation for 
    an upgrade.

    This should be called after an upgrade (so that a background update
    can occur without need to erase a partition).

    @note we need to replace this fn with a new one when we remove upgrade partitions.
*/
UpgradePartitionsState UpgradePartitionsEraseAllManaged(void)
{
    return UPGRADE_PARTITIONS_ERASED;
}

bool UpgradeFWIIFIsCommitStatusAvailable(void)
{
    return TRUE;
}

/******************************************************************************
NAME
        UpgradeFWIIFCompareUpgradePartition
DESCRIPTION
        Compare the two partitions and return the result of difference between them
PARAMS
        partition1 First partition to be compare 
        partition2 Second partition to be compare 
RETURNS
        Returns 0 if both are equal
        -1 second partition is greater than first partition
        1 first partition is greater than second partition
*/
short UpgradeFWIIFCompareUpgradePartition(UpgradePartition partition1, UpgradePartition partition2)
{
    DEBUG_LOG_VERBOSE("UpgradeFWIIFCompareUpgradePartition: partition1 %d inst1 %d partition2 %d inst2 %d", 
    partition1.partitionInstance.partitionID, partition1.partitionInstance.instanceID, 
    partition2.partitionInstance.partitionID, partition2.partitionInstance.instanceID);

    if((partition1.partitionInstance.partitionID == partition2.partitionInstance.partitionID) &&
        (partition1.partitionInstance.instanceID == partition2.partitionInstance.instanceID))
    {
        /* Both partitions are equal */
        return 0;
    }

    if((partition1.partitionInstance.partitionID < partition2.partitionInstance.partitionID) || 
        (partition1.partitionInstance.partitionID == partition2.partitionInstance.partitionID &&
        partition1.partitionInstance.instanceID <= partition2.partitionInstance.instanceID))
    {
        /* partition1 is lesser partition than partition2 */
        return -1;
    }
    else
    {
        return 1;
    }
}

bool UpgradeFWIIFIsUpgradableRAPartition(UpgradePartition partition)
{
    return (partition.partitionInstance.partitionID == IMAGE_SECTION_RA_PARTITION &&
            partition.partitionInstance.instanceID == upgradeFWIFGetConfig()->upgradable_ra_partition_instance) ?
            TRUE:
            FALSE;
}

Source UpgradeFWIIFStreamGetSource(UpgradePartition partition)
{
    if(UpgradeFWIIFIsUpgradableRAPartition(partition))
    {
        return StreamImageRaPartitionStreamGetSource(partition.partitionInstance.instanceID, 0);
    }
    else
    {
        return ImageUpgradeInstanceStreamGetSource(partition.partitionInstance.partitionID, partition.partitionInstance.instanceID);
    }
}

Source UpgradeFWIIFStreamGetSourceFromOffset(UpgradePartition partition, uint32 offset)
{
    if(UpgradeFWIIFIsUpgradableRAPartition(partition))
    {
        return StreamImageRaPartitionStreamGetSource(partition.partitionInstance.instanceID, offset);
    }
    else
    {
        return ImageUpgradeInstanceStreamGetSourceFromOffset(partition.partitionInstance.partitionID, partition.partitionInstance.instanceID, offset);
    }
}

bool UpgradeFWIFIsDfuBankBusy(void)
{
    return OnchipCoredumpIsPresent(ONCHIP_COREDUMP_MAXIDUMP);
}

